﻿using System;
using System.Text;
using System.Xml.Schema;

namespace Evaluate_Arithmetic_Expression
{
    public static class EvaluateExpression        
    {
        private static StringBuilder infix = new StringBuilder();
        private static StringBuilder postfix = new StringBuilder();
        private static Stack<char> operatorsStack = new Stack<char>();
        private static Stack<double> numberStack = new Stack<double>();

        public static double Evaluate(string expression)
        {
            infix.Append(expression);
            infix = new StringBuilder(infix.ToString().Replace(" ", ""));
            ValidateExpression();

            try
            {
                ConvertToPostfix();
                return EvalutePostfixExpression();
            }
            catch (Exception ex)
            {                
                throw new EvaluateExpressionException("Expression has wrong foramt", ex.InnerException);
            }            
        }

        private static void ConvertToPostfix()
        {
            operatorsStack.Push('(');
            infix.Append(')');

            for (int index = 0; !operatorsStack.IsEmpty(); index++)
            {
                char character = infix[index];

                if (char.IsDigit(character) || character == ',')
                {
                    postfix.Append(character);
                    if (!char.IsDigit(infix[index + 1]) && infix[index + 1] != ',')
                        postfix.Append(" ");
                }
                else if (character == '(')
                    operatorsStack.Push(character);
                else if (OperatorPrecedence(character) > -1)
                {
                    postfix.Append(" ");
                    while (true)
                    {
                        char op = operatorsStack.Pop();

                        if (OperatorPrecedence(op) > -1 && Precedence(op, character))
                        {
                            postfix.Append(op);
                        }
                        else
                        {
                            operatorsStack.Push(op);
                            break;
                        }
                    }

                    operatorsStack.Push(character);
                }
                else if (character == ')')
                {
                    char op;

                    while (true)
                    {
                        op = operatorsStack.Pop();

                        if (op != '(')
                        {
                            postfix.Append(op);
                        }
                        else
                        {
                            operatorsStack.Push(op);
                            break;
                        }
                    }

                    operatorsStack.Pop();
                }
            }
        }        

        private static double EvalutePostfixExpression()
        {
            double result = 0;
            double value = 0;
            bool isBigNumber = false;
            bool isFloatingNumber = false;
            double multiplier = 0.1;

            postfix.Append(')');

            for (int index = 0; postfix[index] != ')'; index++)
            {
                char character = postfix[index];

                if (char.IsDigit(character))
                {
                    if (isFloatingNumber)
                    {
                        value += (character - '0')*multiplier;
                        multiplier *= 10;
                    }
                    else
                    {
                        value *= 10;
                        value += (character - '0');
                    }                    
                    isBigNumber = true;
                }
                else if (character == ',')
                    isFloatingNumber = true;
                else if (character == ' ' && (isBigNumber || isFloatingNumber))
                {
                    numberStack.Push(value);
                    value = 0;
                    multiplier = 0.1;
                    isBigNumber = false;
                    isFloatingNumber = false;
                }                                
                else if (OperatorPrecedence(character) > -1)
                {
                    double x, y;
                    x = numberStack.Pop();
                    y = numberStack.Pop();

                    result = Calculate(y, x, character);

                    numberStack.Push(result);
                }
            }

            return result;
        }

        private static double Calculate(double x, double y, char exOperator)
        {
            double result;

            if(exOperator == '+')
                result = (double)Convert.ChangeType((x + y), typeof(double));
            else if(exOperator == '-')
                result = x - y;
            else if(exOperator == '*')
                result = x * y;
            else //if (exOperator == '/')
                result = x / y;

            return result;
        }

        private static bool Precedence(char operator1, char operator2)
        {
            if (OperatorPrecedence(operator1) >= OperatorPrecedence(operator2))
                return true;

            return false;
        }

        private static int OperatorPrecedence(char character)
        {
            switch (character)
            {
                case '+':
                    return 1;
                case '-':
                    return 1;
                case '*':
                    return 2;
                case '/':
                    return 2;

                default:
                    return -1;
            }
        }

        private static void ValidateExpression()
        {
            foreach (char c in infix.ToString())
                if (!char.IsDigit(c) && OperatorPrecedence(c) == -1 && c != '(' && c != ')')               
                    throw new EvaluateExpressionException("Expression has wrong foramt");                         
        }
    }
}
